/*
  Copyright (C) <2009> 

  <Rishi B Agrawal> <Vineet Agarwal> <Rohit Sharma>
  <rishi.b.agrawal@gmail.com> <checkout.vineet@gmail.com> <imreckless@gmail.com>
  
  This program is free software: you can redistribute it and/or modify it 
  under the terms of the GNU General Public License as published by the 
  Free Software Foundation, either version 3 of the License, or (at your 
  option) any later version->

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE->  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <linux/kernel.h>
#include <linux/buffer_head.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/namei.h>
#include <linux/mount.h>
#include <linux/ext2_fs.h>
#include <linux/ext2_fs_sb.h>
#include <linux/path.h>
#include <linux/dcache.h>
#include <../fs/ext2/ext2.h>
#include <linux/time.h>
#include <linux/writeback.h>
#include "../include/ohsm.h"
#include "ohsm_k.h"
#include <linux/quotaops.h>
#include <linux/sched.h>
#include <linux/capability.h>

#define DEVICE_NAME "OHSM_KERNEL_DRIVER"

static int device_ioctl (struct inode *, struct file *, 
		unsigned int, unsigned long);
								
/****************************************************************		 
 *								*
 * reset_sam() is used to reset the sam table			*
 * which is declared in ext2/balloc.c				*
 *								*
 ***************************************************************/

void reset_sam(void) {
	
	int i = 0;
	int j;
	
	ohsm_printk ("\nOHSM: Resetting SAM table.");
	for (i = 0; i < MAX_TIER_ALLOWED; i++) {
		for (j = 0; j < (MAX_DISK_ALLOWED * 2 + 1); j++) {
			 sam[i][j] = 0;
		}
		ohsm_printk ("\n");
	}
	ohsm_printk ("\nOHSM: Resetting SAM table done.");
}

/****************************************************************		 
 *								* 
 * ohsm_free_reloc_policy () is used to free the		*
 *  the relocation policy table 				*
 *								*
 ***************************************************************/
/*
void
ohsm_free_reloc_policy(void) {
	
	int i;
	
	reloc_policy_t = NULL;
	ohsm_printk ("\nOHSM: Relocation policy freed.");
}
*/

/****************************************************************	
 *								*
 * ohsm_disable () disables OHSM and free all data 		*
 * structures allocated						*
 *								*
 ***************************************************************/

int 
ohsm_disable(void) {

	if (ohsm_relocating) {
		return ERR_RELOC_ON;
	}
	
	if (!ohsm_enabled) {
		return ERR_ALREADY_DISABLED;
	}
	
	ohsm_enabled = UNSET;
	ohsm_sb = NULL;
	ohsm_sbi = NULL;
	ohsm_printk ("\nOHSM: Freeing the Allocation policy.");
	reset_sam();
	ohsm_printk ("\nOHSM: Allocation policy freed.");
	
	return SUCCESS;
}

/****************************************************************	
 *								*
 * verify_mntpt () verify whether the mountpoint entered	*
 * is valid and whether it contain a valid ext2 partition	*
 *								*					
 ***************************************************************/

int
verify_mntpt (char *mountpoint) {

	int retval = 0;

	strcpy(ohsm_mountpoint, mountpoint);
	retval = path_lookup (mountpoint, LOOKUP_OPEN, &nd);

	ohsm_printk ("\nOHSM: Verifying mount point = %s.",mountpoint);	
	ohsm_printk ("\nOHSM: Path lookup returned = %d.",retval);
	ohsm_printk ("\nOHSM: Fetched Mountpoint = %s.", nd.path.mnt->mnt_mountpoint->d_name.name);

	if (!strcmp (nd.path.mnt->mnt_mountpoint->d_name.name, mountpoint + 1)) {
		ohsm_sb = nd.path.mnt->mnt_sb;
	}
	else {
		printk("\nOHSM: Invalid mountpoint.");
		return ERR_MNTPNT;
	}	

	if (ohsm_sb == NULL) {
		printk ("\nOHSM: OHSM Super block not found.");
		return ERR_NULL_PTR;
	}

	ohsm_sbi = (struct ext2_sb_info *) (ohsm_sb->s_fs_info);
	if (ohsm_sbi == NULL) {
		printk ("\nOHSM: Incore OHSM superblock is NULL.");
		return ERR_NULL_PTR;
    	}
	path_put (&(nd.path));
	return SUCCESS;
}

/****************************************************************	
 *								*
 *  ohsm_check_age () checks the access age for the inode	*
 *  time - inode access time 					*
 *  check_val - policy value 					*
 *  rel_op - relational operator				*
 *								*
 ***************************************************************/

int
ohsm_check_age (struct timespec time, long long check_val,
										unsigned int rel_op) {

	unsigned int days;
	struct timespec now = current_fs_time (ohsm_sb);
	unsigned long diff_sec = now.tv_sec - time.tv_sec;
	
	days = diff_sec / (24 * 60 * 60);
	
	ohsm_printk ("\nOHSM: Days = %u < Policy value (check_val) = %lld.", days, check_val);
	
	switch (rel_op)
	{
		case LT:
			ohsm_printk ("\nOHSM: IN CHECK AGE LT.");
			return (days < check_val);
	
		    case GT:
			ohsm_printk ("\nOHSM: IN CHECK AGE GT.");
			return (days > check_val);
	
		case EQ:
			ohsm_printk ("\nOHSM: IN CHECK AGE EQ.");
			return (days == check_val);
		
		case NEQ:
			ohsm_printk ("\nOHSM: IN CHECK AGE NEQ.");
			return (days != check_val);
	
		case LE:
			ohsm_printk ("\nOHSM: IN CHECK AGE LE.");
			return (days <= check_val);

		case GE:
			ohsm_printk ("\nOHSM: IN CHECK AGE GE.");
			return (days >= check_val);
		}
	return FALSE;
}

/****************************************************************
 *								*	
 *  ohsm_check_param () checks the access age for the inode	*
 *  input - inode size in KB					*
 *  check_val - policy value 					*
 *  rel_op - relational operator				*
 *								*
 ***************************************************************/

int
ohsm_check_param (long long input, long long check_val,
									unsigned char rel_op) {
	
	ohsm_printk ("\nOHSM: Operator = %u.", rel_op);
	ohsm_printk ("\nOHSM: Input Values : %lld.", input);
	ohsm_printk ("\nOHSM: Value To Be checked %lld.", check_val);
	
	switch (rel_op)
	{
		case LT:
			ohsm_printk ("\nOHSM: Inside LT.");
			return (input < check_val);
		case GT:
			ohsm_printk ("\nOHSM: Inside GT.");
			return (input > check_val);
		case EQ:
			ohsm_printk ("\nOHSM: Inside EQ.");
			return (input == check_val);
		case NEQ:
			ohsm_printk ("\nOHSM: Inside NEQ.");
			return (input != check_val);
		case LE:
			ohsm_printk ("\nOHSM: Inside LE.");
			return (input <= check_val);
		case GE:
			ohsm_printk ("\nOHSM: Inside GE.");
			return (input >= check_val);
		}
	return FALSE;
}

/****************************************************************
 *								*	
 * ohsm_check_fiot () For Future Implementation			*
 *								*
 ***************************************************************/

int
ohsm_check_fiot (struct inode *temp_inode, long long check_val,
		 unsigned int rel_op) {

	long long temp_fiot = 0;
	temp_fiot++;
	return SUCCESS;
}

/****************************************************************
 *								*	
 * ohsm_check_fat () For Future Implementation			*
 *								*
 ***************************************************************/

int
ohsm_check_fat (struct inode *temp_inode, long long check_val,
		unsigned int rel_op) {

	long long temp_fat = 0;
	
	temp_fat++;
	return SUCCESS;
}

/****************************************************************
 *								*	
 *  ohsm_check_qualifying_policy () sets destination		*
 *  tier id for the qualified inode				*
 *								*
 ***************************************************************/

int
ohsm_check_qualifying_policy (struct inode *temp_inode,
			      struct ohsm_relocation_policy *policy) {

	unsigned int val = 1;
	struct ext2_inode_info *ei = NULL;
	
	ei = EXT2_I (temp_inode);
	if (ei == NULL ) {
		ohsm_printk ("\nOHSM: Incore inode is NULL.");
	}

	ohsm_printk ("\nOHSM: Src tier of policy = %u.", policy->src_tier);
	ohsm_printk ("\nOHSM: Dest tier of policy = %u.", policy->dest_tier);
	ohsm_printk ("\nOHSM: Home tier of inode = %u.", ei->ohsm_home_tid);
	
	if (ei->ohsm_home_tid == policy->src_tier) {
		
		ohsm_printk ("\nOHSM: Source tier matched.");
		if (policy->reloc_criteria_bitmap == 0) {
			ohsm_printk ("\nOHMS: Unconditional policy found.");
			ei->ohsm_dest_tid = policy->dest_tier;
			return TRUE;
		}
		
		if (policy->reloc_criteria_bitmap & FSIZE) {
			ohsm_printk ("\nOHSM: Policy based on size being tested.");
			val = ohsm_check_param ((temp_inode->i_size/1024), policy->values[0], policy->rel_ops[0]);
			if (!val) {
				ohsm_printk ("\nOHSM: Policy based on size didn't match.");
				return FALSE;
			}
			ohsm_printk ("\nOHSM: Policy based on size passed.");
		}
		
		if (policy->reloc_criteria_bitmap & FAA) {
			
			ohsm_printk ("\nOHSM: Policy based on FAA being tested.");
			val = val & ohsm_check_age (temp_inode->i_atime, policy->values[1], policy->rel_ops[1]);
			if (!val) {
				ohsm_printk ("\nOHSM: Policy based on FAA didn't match.");
				return FALSE;
			}
			ohsm_printk ("\nOHSM: Policy based on FAA passed.");
		}
		
		if (policy->reloc_criteria_bitmap & FMA) {
			
			ohsm_printk ("\nOHSM: Policy based on FMA being tested.");
			val = val & ohsm_check_age (temp_inode->i_mtime,  policy->values[2], policy->rel_ops[2]);
			 if (!val) {
				ohsm_printk ("\nOHSM: Policy based on FMA didn't match.");
				return FALSE;
			}
			ohsm_printk ("\nOHSM: Policy based on FMA passed.");
		}
		
		if (policy->reloc_criteria_bitmap & FIOT) {
			val = val & ohsm_check_fiot (temp_inode, policy->values[3],  policy->rel_ops[3]);
			if (!val) {
				return FALSE;
			}
		}
		if (policy->reloc_criteria_bitmap & FAT) {
			val = val & ohsm_check_fat (temp_inode, policy->values[4], policy->rel_ops[4]);
			if (!val) {
				return FALSE;
			}
		}
	} else {
		ohsm_printk ("\nOHSM: None of the rules matched.");
		return FALSE;
	}

	ei->ohsm_dest_tid = policy->dest_tier;
	mark_inode_dirty (temp_inode);
	ohsm_sync_inode(temp_inode);
	return TRUE;
}

/****************************************************************
 *								*	
 * ohsm_find_dest_for_rule () find the destination		*
 * tier for relocation						*
 *								*	
 ***************************************************************/

int
ohsm_find_dest_for_rule (struct inode *temp_inode, int rule_no) {

	int policy_no = 0;
	int ret = 0;
	
	ohsm_printk ("\nOHSM: Finding destination for relocation.");
	for (policy_no = 0; policy_no < relocpol->cnt_policies; policy_no++) {
		
		ohsm_printk ("\nChecking against policy number = %d.", policy_no);
		ret = ohsm_check_qualifying_policy (temp_inode, &relocpol->policy[rule_no]);
		if (ret == TRUE) {
			ohsm_printk ("\nOHSM: Policy number %d qualified.", policy_no);
			return TRUE;
		}
	}
	return FALSE;
}

/****************************************************************
 *								*		
 *  ohsm_realloc_mcp () actual relocation code performing	*
 *  relocation.							*
 *  Relocation is done using Tricky Copy And Swap Algorithm	*
 *  inum - inode no. of the inode that is to be relocated	*
 *								*
 ***************************************************************/

int ohsm_realloc_mcp (unsigned long inum) {

	int err = 0;
	int done;
	int blk_index = 0;
	unsigned int swap;
	loff_t i_size;
	blkcnt_t nblocks;
	struct inode *src_ind, *dest_ind;
	struct buffer_head *src_bhptr = NULL, *dst_bhptr = NULL;
	struct buffer_head dest_bh, src_bh;

	/* Get the source inode */
	src_ind = ext2_iget (ohsm_sb, inum);
	if (!src_ind) {
		printk ("\nOHSM: Source Inode Not Found ");
		goto out_err;
	}

	/* Getting the ghost inode */
	dest_ind = ext2_new_inode (nd.path.dentry->d_inode, src_ind->i_mode);
	if (IS_ERR(dest_ind)) {
		err = ERR_FS;
		printk ("\nOHSM: destination inode unable to allocate. err = %ld.", PTR_ERR(dest_ind));
		iput(src_ind);
		goto out_err;
	}
	
	EXT2_I(dest_ind)->ohsm_home_tid = EXT2_I(src_ind)->ohsm_dest_tid;
	/* Locking the source inode using mutex */
	mutex_lock (&src_ind->i_mutex);

	/* Get the source inode size and number of blocks */
	i_size = i_size_read(src_ind);
	
	/* Calculating number of block to allocate for ghost inode */
	nblocks = (i_size >> EXT2_BLOCK_SIZE_BITS(ohsm_sb)) +
		   ((i_size & (ohsm_sb->s_blocksize - 1)) ? 1 : 0);

	/*  Starting Tricky Copy
	 *
	 *  Allocate a data block for ghost
	 *  Read the block into buffer head 1
	 *  Read the source block into buffer head 2
	 *  assign b_data of buffer 2 to b_data of buffer 1
	 *  Mark buffer dirty
	 *  For indirect blocks read the chain of indirect
	 *  pointers leading to data  
	 *  Now ghost inode has exact data from source
	 *  Swap source i_data with ghost i_data
	 *  Finally source inode has new data blocks
	 *  
	 *  so mark it dirty and delete the ghost inode. 
	 */
	
	for (done = 0; done < nblocks; done++) {
		memset(&dest_bh, 0, sizeof(struct buffer_head));
		memset(&src_bh, 0, sizeof(struct buffer_head));

       	err = ext2_get_block (src_ind, done, &src_bh, 0);
		if (err < 0) {
			err = ERR_FS;
			printk ("\nOHSM: error getting blocks ret_val = %d.", err);
			goto unlock;
		}
		
		if (!buffer_mapped(&src_bh)) {
			err = ERR_FS;
			ohsm_printk ("\nOHSM: HOLE found.");
			continue;
		}

		dest_bh.b_state = 0;
		err = ext2_get_block (dest_ind, done, &dest_bh, 1);
		if (err < 0) {
			err = ERR_FS;
			printk ("\nOHSM: error getting blocks. ret_val = %d.", err);
			goto unlock;
		}

		src_bhptr = sb_bread(ohsm_sb, src_bh.b_blocknr); 
		if (!src_bhptr) {
			err = ERR_FS;
			printk("\nOHSM: Source buffer NULL.");
			goto unlock;
		}

		dst_bhptr = sb_bread(ohsm_sb, dest_bh.b_blocknr); 
		if (!dst_bhptr) {
			err = ERR_FS;
			printk("\nOHSM: Destination buffer NULL.");
			goto unlock;
		}		
		lock_buffer(dst_bhptr);
		memcpy(dst_bhptr->b_data,src_bhptr->b_data,src_bhptr->b_size);
		unlock_buffer(dst_bhptr);
		
		mark_buffer_dirty(dst_bhptr);

		brelse(src_bhptr);
		brelse(dst_bhptr); 
	}
			
	for (blk_index = 0; blk_index < 15; blk_index++) {
		if (EXT2_I (dest_ind)->i_data[blk_index]) {
			swap = EXT2_I (src_ind)->i_data[blk_index];
			EXT2_I (src_ind)->i_data[blk_index] =
				EXT2_I (dest_ind)->i_data[blk_index];
			EXT2_I (dest_ind)->i_data[blk_index] = swap;
		}
	}
	
	EXT2_I(src_ind)->ohsm_home_tid = EXT2_I(src_ind)->ohsm_dest_tid;
	EXT2_I(src_ind)->ohsm_dest_tid = 0;
	
	dest_ind->i_blocks = src_ind->i_blocks ;
	mark_inode_dirty (src_ind);
	sync_mapping_buffers(src_ind->i_mapping);
	ohsm_sync_inode(src_ind);  
	err = SUCCESS;

unlock:
	iput(src_ind);
	dest_ind->i_state &= ~I_NEW;
	dest_ind->i_nlink = 0;
	iput(dest_ind);
	mutex_unlock (&src_ind->i_mutex);

out_err:
	return err;
}

/****************************************************************
 *								*	
 * ohsm_get_inodes_for_relocation () scans the complete 	*
 * inode of the file system to get an qualifying inode 		*
 * and send the qualified for relocation			*	
 *								*
 ***************************************************************/

int
ohsm_get_inodes_for_relocation (unsigned long arg)
{
	unsigned char r_bitmap;
	unsigned char start_ino = 0;
	int rule_no = 0, ret;
	int retval = 0;
	unsigned long nr_groups;
	unsigned long rel_inode;
	unsigned long i = 0;
	unsigned long inode_no = 0;
	unsigned long nr_inodes_per_group;
	unsigned long *t = NULL;
	unsigned long count = 0;
	unsigned long rule_bitmap_arg = 0;
	struct buffer_head *bh = NULL;
	struct inode *temp_inode = NULL;
	struct ext2_inode_info *ei = NULL;
	
	t = (unsigned long *)arg;
	rule_bitmap_arg = *t;

	if (!ohsm_enabled) {
		printk ("\nOHSM: OHSM is not enabled. Hence, no relocation.");
		return ERR_RELOC_INACTIVE;
	}

	if (rule_bitmap_arg > ((1 << (relocpol->cnt_policies )) - 1)) {
		printk ("\nOHSM: Invalid relocation rule number passed. Rule = %lu.", rule_bitmap_arg);
		return ERR_RULE_BITMAP;
	 }

	ohsm_printk ("\nOHSM: In RELOCATION CODE.");  
	ohsm_printk ("\nOHSM: The rule bitmap = %lu.", rule_bitmap_arg);
	ohsm_printk ("\nOHSM: The Number of Rules = %d.",relocpol->cnt_policies);
	
	
	nr_groups = ohsm_sbi->s_groups_count;
	nr_inodes_per_group = ohsm_sbi->s_inodes_per_group;	//group
	
	ohsm_printk ("\nOHSM: Number of groups in file system: %lu.", nr_groups);
	ohsm_printk ("\nOHSM: Number of inodes per groups : %lu.", nr_inodes_per_group);

	for (i = 0; i < nr_groups; i++) {
		bh = (struct buffer_head *) read_inode_bitmap (ohsm_sb, i);
		if (!bh) {
			printk ("\nOHSM: BH is null ");
			return ERR_FS;
		}
		if (i == 0) {
			start_ino = 11;
		} else {
			start_ino = 0;
		}
		for (inode_no = start_ino; inode_no < nr_inodes_per_group - 1; inode_no++) {

			ret = ext2_test_bit (inode_no, (unsigned long *) (bh->b_data));
			 if (ret) {
			
				ohsm_printk ("\nOHSM: Matching found. Inode Number = %lu.",inode_no);
				temp_inode = ext2_iget (ohsm_sb, (inode_no + 1) + i * nr_inodes_per_group);
				if (!temp_inode) {
					printk ("\nOHSM : ext2_iget returned NULL.");
					brelse(bh);
					return ERR_FS;
				}

				ei = EXT2_I (temp_inode);
				if (!ei) {
					printk ("\nOHSM: Incore inode is NULL.");
					iput(temp_inode);
					brelse(bh);
					return ERR_FS;
				}
				if (ei->ohsm_home_tid == 0) {
					printk ("\nOHSM: Not relocating as home tid is 0. Inode Number = %ld.", temp_inode->i_ino);
					iput(temp_inode);
					continue;
				} else {
					r_bitmap = rule_bitmap_arg;	
					for (rule_no = 0; rule_no < relocpol->cnt_policies && r_bitmap; rule_no++) {
						
						ohsm_printk ("\nOHSM: Checking for rule Number = %d.", rule_no);
						if (r_bitmap & 1) {
							
							ohsm_printk ("\nOHSM: Checking inode number = %ld.",  temp_inode->i_ino);
							retval = ohsm_find_dest_for_rule (temp_inode, rule_no);
							
							if (retval) {
						
								ohsm_printk ("\nOHSM: Qualified Rule = %d.", rule_no);
								ohsm_printk ("\nOHSM: The Destination TID should have changed now.");
								ohsm_printk ("\nOHSM: inode number = %lu.", temp_inode->i_ino);
								ohsm_printk ("\nOHSM: inode home tier id = %u.", ei->ohsm_home_tid);
								ohsm_printk ("\nOHSM: inode dest tier id = %u.",  ei->ohsm_dest_tid);
								ohsm_printk ("\nOHSM: Relocate inode num = %lu from tier = %u to tier = %u.", temp_inode->i_ino, ei->ohsm_home_tid, ei->ohsm_dest_tid);
								
								rel_inode = temp_inode->i_ino;
								iput (temp_inode);
								ohsm_realloc_mcp(rel_inode);
								count++;
								ohsm_printk ("\nOHSM: Total files relocated till now = %lu",count);

								break;
							} else {
								iput(temp_inode);
							}
						}
						ohsm_printk ("\nOHSM: R Bitmap before SHIFT is = %x ", r_bitmap);
						r_bitmap = r_bitmap >> 1;
					}
				}
			}
		}
		brelse(bh);
	}

	ohsm_printk ("\nOHSM: Relocation performed successfully.");
	return SUCCESS;
}

/****************************************************************
 *								*	
 * debug_print_and_test_type () is used in debugging		*
 * It prints File type and dest tier specified by the 		*
 * user								*		
 *								*
 ***************************************************************/

void
debug_print_and_test_type (struct ohsm_type_tier *ptr,
									unsigned int number) {

	int i = 0;
	
	ohsm_printk ("\n\nOHSM: File type and their destinations.");	
	for (i = 0; i < number; i++) {
		ohsm_printk ("\nOHSM: File Type = %s Dest Tier = %u.",ptr[i].type, ptr[i].tier);
	}  
}

/****************************************************************
 *								*	
 * debug_print_and_test_user () is used in debugging		*
 * It prints UID and dest tier specified by the 		*
 * user								*	
 *								*
 ***************************************************************/

void
debug_print_and_test_user (struct ohsm_uid_tier *ptr, unsigned int number) {

	int i = 0;
	
	ohsm_printk ("\n\nOHSM: UID and their destinations.");	
	for (i = 0; i < number; i++) {
		ohsm_printk ("\nOHSM: UID = %u Dest Tier = %u", ptr[i].uid, ptr[i].tier);
	}
}

/****************************************************************
 *								*						
 * debug_print_and_test_group () is used in debugging		*
 * It prints GID and dest tier specified by the 		*	
 * user								*
 *								*
 ***************************************************************/

void
debug_print_and_test_group (struct ohsm_gid_tier *ptr, 
									unsigned int number) {

	int i = 0;

	ohsm_printk ("\n\nOHSM: GID and their destinations.");	
	for (i = 0; i < number; i++) {
		ohsm_printk ("\nOHSM: Gid = %u Dest Tier = %u", ptr[i].gid, ptr[i].tier);
	}
}

/****************************************************************
 *								*	
 * turn_the_tables () is used to create the sam table		*
 *								*			
 ***************************************************************/

void
turn_the_tables (void) {

	int i = 0, j = 0;
	unsigned int cur_tier;
	unsigned int col;
	unsigned int disks;
	unsigned long nr_groups;
	
	ohsm_printk ("\nOHSM: Initializing the SAM table.");
	for (i = 0; i < MAX_TIER_ALLOWED; i++) {
		for (j = 0; j < MAX_DISK_ALLOWED * 2 + 1; j++) {
			sam[i][j] = 0;
		}	
	}


	ohsm_printk("\nOHSM: Printing the Dev info");

	ohsm_printk("\nOHSM: Number of tiers = %u ", dev_info->nr_tiers);
	ohsm_printk("\nOHSM: Number of devices = %u ", dev_info->nr_devices);

	for(i=0 ; i < dev_info->dinfo_count; ++i) {

	ohsm_printk("\nOHMS: Number = %u ", i);
	ohsm_printk("\nOHSM: Tier =  %u ", dev_info->device[i].tier);
	ohsm_printk("\nOHSM: Device = %u ", dev_info->device[i].dev_num);
	ohsm_printk("\nOHSM Length  = %lu ", dev_info->device[i].len);
	ohsm_printk("\nOHSM Start = %lu ", dev_info->device[i].begin);
	

	}


	for (i = 0; i < dev_info->dinfo_count; i++) {
	
		ohsm_printk ("\n\n\n\n");
		cur_tier = dev_info->device[i].tier;
		sam[cur_tier][0] = sam[cur_tier][0] + 1;
		disks = sam[cur_tier][0];
		col = disks * 2 - 1;
		sam[cur_tier][col] = dev_info->device[i].blkgrp_start;
		sam[cur_tier][col + 1] = dev_info->device[i].blkgrp_end;
	}
#ifdef DEBUG
	ohsm_printk ("\nOHSM: Printing SAM before adding the start and end.");
	debug_print_sam ();
#endif

	nr_groups = ohsm_sbi->s_groups_count;
	ohsm_printk ("\nOHSM: Number of Groups in the File System = %lu.",nr_groups);
	sam[0][0] = 1;
	sam[0][1] = 0;
	sam[0][2] = nr_groups;
	
	for (i = 1; i <= dev_info->dinfo_count; i++) {
		if (sam[i][0] == 0) {
			ohsm_printk ("\nOHSM: SAM table filled.");
			break;
		}
		sam[i][0]++;
		disks = sam[i][0];
		col = disks * 2 - 1;
	
		ohsm_printk ("\nOHSM: sam[joined[i].tier][0] = %lu.", sam[dev_info->device[i].tier][0]);
		ohsm_printk ("\nOHSM: Number of disks on tier = %u is %u.", dev_info->device[i].tier, disks);
		ohsm_printk ("\nOHSM: Printing the columns = %u.", col);
		
		sam[i][col] = 0;
		sam[i][col + 1] = nr_groups;
	}
#ifdef DEBUG
	ohsm_printk ("\nOHSM: Printing SAM after adding start and end.");
	debug_print_sam ();
#endif	
}

/****************************************************************
 *								*	
 * ohsm_set_bg_dm_info is used to calculate the blockgroup	*
 * start and block group end per device				*
 *								*	
 ***************************************************************/

void
ohsm_set_bg_dm_info (void) {

	unsigned int i;
	unsigned int block_size, blk_per_grp;
	int retval = 0;
	struct nameidata nd;
	struct ext2_sb_info *sbi = NULL;
	struct super_block *sb;
	struct vfsmount *vfs = NULL;

	retval = path_lookup (ohsm_mountpoint, LOOKUP_OPEN, &nd);	
	vfs = nd.path.mnt;
	sb = (struct super_block *) vfs->mnt_sb;
	sbi = (struct ext2_sb_info *) sb->s_fs_info;
	block_size = 1 << sbi->s_es->s_log_block_size;
	blk_per_grp = sbi->s_es->s_blocks_per_group;

	for (i = 0; i < dev_info->dinfo_count; i++) {

		dev_info->device[i].blkgrp_start = (unsigned int) (dev_info->device[i].begin /(2 * block_size * blk_per_grp));
		if (dev_info->device[i].begin) {
			dev_info->device[i].blkgrp_start++;
		}

		dev_info->device[i].blkgrp_end =
		(unsigned int) ((dev_info->device[i].begin + dev_info->device[i].len) / (2 * block_size * blk_per_grp) - 1);
		
		if (dev_info->device[i].len < (block_size * blk_per_grp)) {
			dev_info->device[i].blkgrp_end++;
		}
	}
	path_put (&(nd.path));
}

/****************************************************************
 *								*	
 *  ohsm_get_inode_for_debug () is used for debugging		*
 *  it scans all inodes in the file system and prints 		*
 *  them along with their size and tier-id			*
 *								*
 ***************************************************************/

int
ohsm_get_inodes_for_debug (void) {
	
	unsigned char start_ino = 0;
	int ret;
	unsigned long nr_groups;
	unsigned long i = 0;
	unsigned long nr_inodes_per_group;
	unsigned long inode_no = 0;
	struct buffer_head *bh = NULL;
	struct inode *temp_inode = NULL;
	struct ext2_inode_info *ei = NULL;

	nr_groups = ohsm_sbi->s_groups_count;
	nr_inodes_per_group = ohsm_sbi->s_inodes_per_group;

	printk ("\n\nOHSM: Inode No.\tFile Size\tHome TID\n");
		
	for (i = 0, start_ino = 11; i < nr_groups; i++, start_ino = 0) {

		bh = (struct buffer_head *) read_inode_bitmap (ohsm_sb, i);
		if (!bh) {
			printk ("\nOHSM: BH is null.");
			return ERR_FS;
		}

		for (inode_no = start_ino; inode_no < nr_inodes_per_group - 1; inode_no++) {
			ret = ext2_test_bit (inode_no, (unsigned long *) (bh->b_data));

			 if (ret) {
				
				temp_inode = ext2_iget (ohsm_sb, (inode_no + 1) + i * nr_inodes_per_group);
				if (!temp_inode) {
					printk ("\nOHSM: ext2_iget returned NULL.");
					brelse(bh);
					return ERR_FS;
				}

				ei = EXT2_I (temp_inode);
				if (!ei) {
					printk ("\nOHSM: EXT2_I returned NULL.");
					iput(temp_inode);
					brelse(bh);
					return ERR_FS;
				}

				if(temp_inode->i_size < 1024)
					printk ("\n%lu\t\t%lub\t\t%u",(unsigned long) temp_inode->i_ino, (unsigned long) temp_inode->i_size, (unsigned int)ei->ohsm_home_tid);
				else
					printk ("\n%lu\t\t%luk\t\t%u",(unsigned long) temp_inode->i_ino, (unsigned long) temp_inode->i_size >> 10, (unsigned int)ei->ohsm_home_tid);

				iput(temp_inode);
			}
		}
		brelse(bh);
	}
	return SUCCESS;
}

/****************************************************************
 *								*	
 * debug_print_sam () print the sam table conatining the 	*
 * block group range per tier					*
 *								*
 ***************************************************************/

void
debug_print_sam (void) {

	int i = 0;
	int j;
	
	printk ("\nOHSM: Printing SAM Table.\n");
	for (i = 0; i < MAX_TIER_ALLOWED; i++) {
		ohsm_printk ("\n");
		if(sam[i][0]==0)
				continue;
		else
			for (j = 0; j < (MAX_DISK_ALLOWED * 2 + 1); j++) {
				printk (" - %lu - ", sam[i][j]);
				if(sam[i][j] == sam[0][2])
					break;
			}
		printk ("\n");
	}
}

/****************************************************************
 *								*	
 * demo_reloc () is defined for demo. It relocates the		*
 * inode submitted by the user.					*
 *								*
 ***************************************************************/

int
demo_reloc(struct ohsm_debug_reloc *reloc_demo) {
	
	int ret;
	struct inode *src_ind ;

	/* Get the source inode */
	src_ind = ext2_iget (ohsm_sb, reloc_demo->inum);
	
	if (!src_ind) {
		printk (KERN_DEBUG "\nOHSM: Source Inode Not Found, ext2_iget returned NULL.");
		return ERR_INO_NOEXIST;
	}
	
	EXT2_I(src_ind)->ohsm_dest_tid = reloc_demo->dest;
	iput(src_ind);
	ret = ohsm_realloc_mcp(reloc_demo->inum);	
	
	return ret;
}

/****************************************************************
 *								*	
 * device_driver related functions				*
 *								*
 ***************************************************************/

struct file_operations fops = {
	.ioctl = device_ioctl,
	.read = NULL,
	.write = NULL,
};

/****************************************************************
 *								*	
 * ohsm_disable () disables OHSM and free all data 		*	
 * structures allocated						*
 *								*
 ***************************************************************/
int
ohsm_init (void) {

	Major = register_chrdev (0, DEVICE_NAME, &fops);
	if (Major < 0) {
		printk ("\nOHSM: Unable to initialize driver.");
		return Major;
	}
	ohsm_printk ("\nOHSM: Module Initialized.");
	ohsm_enabled = UNSET;
	ohsm_relocating = UNSET;
	ohsm_get_tier_fptr = &ohsm_get_tier;
	return SUCCESS;
}

/****************************************************************
 *								*	
 * ohsm_disable () disables OHSM and free all data		*
 * structures allocated						*
 *					bug			*
 ***************************************************************/
void
ohsm_cleanup (void) {

	int ret;
	ret = ohsm_disable();
	unregister_chrdev (Major, DEVICE_NAME);
	ohsm_get_tier_fptr = NULL;
	ohsm_printk ("\nOSHM: Module unloaded.");
}

/****************************************************************
 *								*
 * ohsm_sanity_check () checks whether the ohsm is currently	*
 * enabled and the superblock of the mounted file system 	*
 * exist.							*
 *								* 
 ***************************************************************/
int
ohsm_sanity_check (void) {

	if (!ohsm_enabled) {
		ohsm_printk ("\nOHSM: OHSM is currently Disabled " );
		return ERR_OHSM_INACTIVE;
	}
	
	else if (!ohsm_sb) {
		printk ("\nOHSM: oshm_sb is null.");
		return ERR_NULL_PTR;
	}
	
	else if (!ohsm_sbi) {
		printk ("\nOHSM: oshm_sbi is null.");
		return ERR_NULL_PTR;
	}

	return SUCCESS;
}

/****************************************************************
 *								*
 * ohsm_disable () disables OHSM and free all data		*
 * structures allocated						*
 *								*
 ***************************************************************/
static int
device_ioctl (struct inode *inode, struct file *filp, 
						unsigned int cmd, unsigned long arg) {

	int ret = 0;
	struct ohsm_debug_reloc *reloc_demo;
	int i=0;
	printk("\n command %u",cmd);

	switch (cmd) {
		case OHSM_ENABLE:
				
				printk(KERN_ALERT"\n I am here %d",i++);
				if (ohsm_enabled) {
					printk ("\nOHSM: OHSM already enabled.");
					return ERR_ALREADY_EN;
				}
				
				printk(KERN_ALERT"\n I am here %d",i++);

				ohsm_admin_info = (struct ohsm_struct_for_ioctl *) arg;

				if (!ohsm_admin_info) {  
					printk ("\nOHSM: NULL passed from userspace.");
					return ERR_NULL_PTR;
				}
						
				ohsm_printk ("\nOHSM: Request to enable OHSM.");
				ohsm_printk ("\nOHSM: The Mount Point = %s.", ohsm_admin_info->mountpoint);
				if (verify_mntpt (ohsm_admin_info->mountpoint)) {
					return ERR_MNTPNT;
				}	
				
				printk(KERN_ALERT"\n I am here %d",i++);
				if (ohsm_admin_info->ptr_dev_file == NULL) {
					printk ("\nOHSM: Device topology is NULL");
					return ERR_NULL_PTR;
				}	

				printk(KERN_ALERT"\n I am here %d",i++);
				if (ohsm_admin_info->ptr_alloc_policy == NULL) {
					printk ("\nOHSM: Allocation policy is NULL");
					return ERR_ALLOC_PTR;
				}	
				
				printk(KERN_ALERT"\n I am here %d",i++);
				if (ohsm_admin_info->ptr_reloc_policy == NULL) {
					printk ("\nOHSM: Relocation policy is NULL.");
					return ERR_RELOC_PTR;
				}	
				
				dev_info = (struct ohsm_tier_dev_info *)kmalloc(sizeof(struct ohsm_tier_dev_info), GFP_KERNEL);

				allocpol = (struct ohsm_allocation_policy *)kmalloc(sizeof(struct ohsm_allocation_policy), GFP_KERNEL);
		
				relocpol = (struct ohsm_reloc_policy *)kmalloc(sizeof(struct ohsm_reloc_policy), GFP_KERNEL);

				if(copy_from_user((void *)dev_info,(void*)ohsm_admin_info->ptr_dev_file,sizeof(struct ohsm_tier_dev_info))) {
					printk(KERN_ALERT "OHSM: error copying dev_info");
					return ERROR;
				}

				if(copy_from_user((void *)allocpol,(void*)ohsm_admin_info->ptr_alloc_policy,sizeof(struct ohsm_allocation_policy))) {
					printk(KERN_ALERT "OHSM: error copying allocation policy");
					return ERROR;
				}

				if(copy_from_user((void *)relocpol,(void*)ohsm_admin_info->ptr_reloc_policy,sizeof(struct ohsm_reloc_policy))) {
					printk(KERN_ALERT "OHSM: error copying relocation policy");
					return ERROR;
				}

				printk(KERN_ALERT"\n I am here %d",i++);
				printk("\n allocpol->cnt_types = %d",allocpol->cnt_types);
				printk("\n dev tier= %u", dev_info->device[1].tier);
				ohsm_set_bg_dm_info ();	
				turn_the_tables();		
			
				printk(KERN_ALERT"\n I am here %d",i++);
				ohsm_enabled = 1;
				return SUCCESS;

	 	case OHSM_TRIGGER_RELOCATION:
				ret = ohsm_sanity_check();
				if (ret) 
					return ret;
				ohsm_relocating = SET;
				ret = ohsm_get_inodes_for_relocation (arg);
				ohsm_relocating = UNSET;
				ohsm_printk ("\nOHSM: Relocation process done, returning with ret = %d.",ret);
				return ret;	

		case DISABLE_OHSM:
				if (ohsm_relocating) {
					return ERR_RELOC_ON;
				}
				ret = ohsm_sanity_check();
				if (ret) 
					return ret;
				return ohsm_disable();

		case DISPLAY_SAM:
				ret = ohsm_sanity_check();
				if (ret) 
					return ret;
				debug_print_sam();
				return SUCCESS;

		case DISPLAY_INODES:
				ret = ohsm_sanity_check();
				if (ret) 
					return ret;
				ret = ohsm_get_inodes_for_debug ();	
				return ret;

		case DEMO_RELOC:
				ret = ohsm_sanity_check();
				if (ret) 
					return ret;
				reloc_demo = (struct ohsm_debug_reloc *) arg;
				ret = demo_reloc(reloc_demo);
				return ret;
		
		case OHSM_STATUS :
				if (ohsm_enabled)
					ohsm_printk ("\nOHSM: OHSM is currently Enabled." );
				else
					ohsm_printk ("\nOHSM: OHSM is currently Disabled " );
				
				return !ohsm_enabled ;

		case OHSM_GET_TIER_INFO:
				ret = ohsm_sanity_check();
				if(ret)
					return ret;
				
				BUG_ON(dev_info == NULL);

				if(copy_to_user((void*)arg,(void*)dev_info,sizeof(struct ohsm_tier_dev_info)))
					return ERR_DEVICE;
				return SUCCESS;

		case OHSM_GET_ALLOC_POL:
				ret = ohsm_sanity_check();
				if(ret)
					return ret;
				
				BUG_ON(allocpol == NULL);
				if(copy_to_user((void*)arg,allocpol,sizeof(struct ohsm_allocation_policy)))
					return ERR_ALLOC;
				return SUCCESS;

		case OHSM_GET_RELOC_POL:
				ret = ohsm_sanity_check();
				if(ret)
					return ret;
				
				BUG_ON(relocpol == NULL);
				if(copy_to_user((void*)arg,relocpol,sizeof(struct ohsm_reloc_policy)))
					return ERR_RELOC;
				return SUCCESS;
	}
	return SUCCESS;
}
module_init(ohsm_init);
module_exit(ohsm_cleanup);
MODULE_LICENSE ("GPL v2");
MODULE_AUTHOR ("Rishi Bhushan Agrawal, Vineet Agarwal, Rohit Sharma");
